package ph.gov.marikina.mcsmtms.services.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.Period;

import ph.gov.marikina.mcsmtms.constants.GlobalConstants;
import ph.gov.marikina.mcsmtms.constants.GlobalConstants.Roads;
import ph.gov.marikina.mcsmtms.constants.GlobalConstants.SituationTypes;
import ph.gov.marikina.mcsmtms.constants.GlobalConstants.Status;
import ph.gov.marikina.mcsmtms.constants.GlobalConstants.TrafficConditionTypes;
import ph.gov.marikina.mcsmtms.dao.SituationReportDAO;
import ph.gov.marikina.mcsmtms.dao.UserDAO;
import ph.gov.marikina.mcsmtms.domain.SituationReport;
import ph.gov.marikina.mcsmtms.domain.SituationSegment;
import ph.gov.marikina.mcsmtms.domain.User;
import ph.gov.marikina.mcsmtms.services.SettingsService;
import ph.gov.marikina.mcsmtms.services.SituationReportService;
import ph.gov.marikina.mcsmtms.services.SmsSenderService;
import ph.gov.marikina.mcsmtms.utils.Utils;

public class SituationReportServiceImpl implements SituationReportService {
	
	/** The user dao. */
    private SituationReportDAO situationReportDAO;
    
    private UserDAO userDAO;
	
	private SmsSenderService smsSenderService;
	
	private SettingsService settingsService;
	/**
	 * Sets the user dao.
	 *
	 * @param userDAO the new user dao
	 */
    
//    @Override
	public void setSituationReportDAO(SituationReportDAO situationReportDAO) {
		this.situationReportDAO = situationReportDAO;
	}
	
	public void setUserDAO(UserDAO userDAO) {
		this.userDAO = userDAO;
	}
	
	public void setSmsSenderService(SmsSenderService smsSenderService) {
		this.smsSenderService = smsSenderService;
	}
	
	@Override
	public void addSituationReport(SituationReport situationReport) {
		try {
			Integer situationReportId = situationReportDAO.addSituationReport(situationReport);
			
			if(situationReportId != null &&
				situationReport.getStatus() != null &&
					situationReport.getStatus().equals(Status.VALIDATED.getCode())) {
				// send notification message
				
				sendAlertMessages(situationReport);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	private void sendAlertMessages(SituationReport situationReport) {
		String alertMessage = Utils.generateAlertMessage(situationReport);
		
		List<User> alertUserList = userDAO.getAlertUsers();
		
		for(User user : alertUserList) {
			if(user.isHasMobileAlert() &&
					user.getMobileNumber() != null) { 
				smsSenderService.sendSms(user.getMobileNumber(), alertMessage);
			}
		}
	}
	
	@Override
	public List<SituationReport> getAllSituationReports() {
		List<SituationReport> situationReportsList = situationReportDAO.getAllSituationReports();
		
		for(SituationReport situationReport : situationReportsList) {
			situationReport.setRemarks(Utils.generateAlertMessage(situationReport));
		}
		
		return situationReportsList;
	}
	
	@Override
	public List<SituationReport> getHazardMapSituationReports() {
		List<SituationReport> situationReportsList = situationReportDAO.getHazardMapSituationReports();
		
		//replaceLookUpCodes(situationReportsList);
		
		return situationReportsList;
	}
	
	@Override
	public List<SituationReport> getHazardMapSituationReports(String direction) {
		List<SituationReport> situationReportsList = situationReportDAO.getHazardMapSituationReports();
		
		//replaceLookUpCodes(situationReportsList);
		
		return situationReportsList;
	}
	
	@Override
	public List<SituationReport> getNotifications() {
		return situationReportDAO.getNotifications();
	}
	
	@Override
	public SituationReport approveSituation(String id) {
		SituationReport situationReport = null;
		try {
			situationReport = situationReportDAO.getSituation(id);
			situationReport.setStatus(Status.VALIDATED.getCode());
			
			User user = situationReport.getUser();
			if(user.getCredibilityRating() < GlobalConstants.MAX_CREDIBILITY_RATING) {
				user.setCredibilityRating(user.getCredibilityRating()+1);
				userDAO.updateUser(user);
			}
			
			boolean updateSuccessful = situationReportDAO.updateSituation(situationReport);
			if(updateSuccessful &&
					situationReport.getStatus() != null &&
					situationReport.getStatus().equals(Status.VALIDATED.getCode())) {
				sendAlertMessages(situationReport);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		
		return situationReport;
	}
	
	@Override
	public void setSituationForVerification(String id) {
		SituationReport situationReport = situationReportDAO.getSituation(id);
		situationReport.setStatus(Status.FOR_VALIDATION.getCode());
		situationReportDAO.updateSituation(situationReport);
	}
	
	@Override
	public SituationReport rejectSituation(String id) {
		SituationReport situationReport = situationReportDAO.getSituation(id);
		situationReport.setStatus(Status.REJECTED.getCode());
		situationReportDAO.updateSituation(situationReport);
		
		User user = situationReport.getUser();
		if(user.getCredibilityRating() > GlobalConstants.MIN_CREDIBILITY_RATING) {
			user.setCredibilityRating(user.getCredibilityRating()-1);
			
			if(user.getCredibilityRating() <= GlobalConstants.MIN_CREDIBILITY_RATING) {
				user.setBannedDate(settingsService.getDefaultSystemDate());
			}
			
			userDAO.updateUser(user);
		}
		return situationReport;
	}

	@Override
	public List<SituationReport> getForValidationNotifications() {
		List<SituationReport> notificationList = situationReportDAO.getForValidationNotifications();
		
		//replaceLookUpCodes(notificationList);
		
		return notificationList;
	}
	
	@Override
	public List<SituationReport> getForValidationNotifications(String road) {
		List<SituationReport> notificationList = situationReportDAO.getForValidationNotifications(road);
		
		return notificationList;
	}
	
	private void replaceLookUpCodes(List<SituationReport> situationReportsList) {
		if(situationReportsList == null) return;
		
		for(SituationReport situationReport : situationReportsList) {
			situationReport.setRemarks(Utils.generateAlertMessage(situationReport));
			situationReport.setRoad(Roads.getDescriptionByCode(situationReport.getRoad()));
			
			for(SituationSegment segment : situationReport.getSituationSegmentList()) {
				segment.setSituation(SituationTypes.getDescriptionByCode(segment.getSituation()));
				segment.setTrafficCondition(TrafficConditionTypes.getDescriptionByCode(segment.getTrafficCondition()));
			}
		}
	}
	
	private void replaceLookUpCodes(SituationReport situationReport) {
	
		if(situationReport == null) return;
		situationReport.setRemarks(Utils.generateAlertMessage(situationReport));
		situationReport.setRoad(Roads.getDescriptionByCode(situationReport.getRoad()));
		
		for(SituationSegment segment : situationReport.getSituationSegmentList()) {
			segment.setSituation(SituationTypes.getDescriptionByCode(segment.getSituation()));
			segment.setTrafficCondition(TrafficConditionTypes.getDescriptionByCode(segment.getTrafficCondition()));
		}
	}
	
	@Override
	public List<SituationReport> getValidReportsForDateRange(Date startDate, Date endDate) {
		//if(reportType.equals("daily")) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
//		} else {
//			
//		}
		
		List<SituationReport> situationReportList = situationReportDAO.getReportsForDateRange(startDate, endDate, Status.VALIDATED.getCode()); 
		
		//replaceLookUpCodes(situationReportList);
				
		return situationReportList;
	}
	
	@Override
	public Map<String, Map<String, Integer>> getSituationCountsForDateRange(Date startDate, Date endDate) {
		//if(reportType.equals("daily")) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
		
		List<Object[]> situationCountsList = situationReportDAO.getSituationCountsForDateRange(startDate, endDate, Status.VALIDATED.getCode());
		Map<String, Map<String, Integer>> situationCountsMap = new HashMap<String, Map<String,Integer>>();
		for (Roads road : Roads.values()) {
			//roadCountMap.put(road.getDescription(), 0);
			Map<String, Integer> situationTypeCountMap = new HashMap<String, Integer>();
			for (SituationTypes situationType : SituationTypes.values()) {
				situationTypeCountMap.put(situationType.getDescription(), 0);
			}
			situationCountsMap.put(road.getDescription(), situationTypeCountMap);
		}
		
		for(Object[] situationCountDetails : situationCountsList) {
			if(null != situationCountDetails[0] && null != situationCountDetails[1]) {
				String roadCode = situationCountDetails[0].toString();
				String situationTypeCode = situationCountDetails[1].toString();
				int count = Integer.parseInt(situationCountDetails[2].toString());
			
				String roadDesc = Roads.getDescriptionByCode(roadCode); 
				String situationTypeDesc = SituationTypes.getDescriptionByCode(situationTypeCode);
				Map<String, Integer> situationTypeCountMap = situationCountsMap.get(roadDesc);
				situationTypeCountMap.put(situationTypeDesc, count);
			}
		}
		
		for (String road : situationCountsMap.keySet()) {
			//roadCountMap.put(road.getDescription(), 0);
			Map<String, Integer> situationTypeCountMap = situationCountsMap.get(road);
			
			//Map<String, Integer> situationTypeCountMap = new HashMap<String, Integer>();
			int sum = 0;
			for (String situationType : situationTypeCountMap.keySet()) {
				//situationTypeCountMap.put(situationType.getCode(), 0);
				int count = situationTypeCountMap.get(situationType);
				sum += count;
			}
			
			situationTypeCountMap.put("Total", sum);
		}
		
		return situationCountsMap;
	}
	
	@Override
	public List<Map<String, Object>> getFrequentlyFloodedStreets(Date startDate, Date endDate) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
		
		List<Object[]> floodReportResultList = situationReportDAO.getFrequentlyFloodedStreets(startDate, endDate, Status.VALIDATED.getCode());
		
		List<Map<String, Object>> floodReportList = new ArrayList<Map<String, Object>>();
		
		for(Object[] floodReport : floodReportResultList) {
			if(null != floodReport[0] && null != floodReport[1]) {
				
				Map<String, Object> floodReportMap = new HashMap<String, Object>();
				
				String road = floodReport[0].toString();
				Date reportDate = (Date) floodReport[1];
				Date reportLastDate = (Date) floodReport[2];
				
//				DateTime reportDateTime = new DateTime(reportDate);
//				DateTime reportLastDateTime = new DateTime(reportLastDate);
//				
//				report
//				
//				//Hours hoursInterval = Hours.hoursBetween(reportDateTime, reportLastDateTime);
				Interval interval = getInterval(reportDate, reportLastDate);
				
				double minutesToHourPercentage = (interval == null) ? 0 : interval.toDuration().getStandardMinutes() / (60 * 24 * 1.0);
				floodReportMap.put("road", Roads.getShortNameByCode(road));
				floodReportMap.put("fromDate", Utils.formatDate(reportDate, Utils.ganttDateFormatString));
				floodReportMap.put("toDate", Utils.formatDate(reportLastDate, Utils.ganttDateFormatString));
				//floodReportMap.put("interval", interval.toDuration().getStandardDays() + " " + (interval.toDuration().getStandardHours() - interval.toDuration().getStandardDays() * 24));
				floodReportMap.put("interval", getCompleteIntervalDescription(interval));
				floodReportMap.put("durationInMinutes", minutesToHourPercentage);
				
				floodReportList.add(floodReportMap);
				//System.out.println(road + ": " + reportDate + ": "  + reportLastDate + ": " + interval.toDuration());
			}
		}
		
		return floodReportList;
	}
	
	@Override
	public List<Map<String, Object>> getMostAccidentProneStreets(Date startDate, Date endDate) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
		
		List<Object[]> mostAccidentProneList = situationReportDAO.getMostAccidentProneStreets(startDate, endDate, Status.VALIDATED.getCode());
		
		List<Map<String, Object>> mostAccidentProneReportList = new ArrayList<Map<String, Object>>();
		
		for(Object[] mostAccidentProne : mostAccidentProneList) {
			if(null != mostAccidentProne[0] && null != mostAccidentProne[1]) {
				
				Map<String, Object> mostAccidentProneMap = new HashMap<String, Object>();
				
				String road = mostAccidentProne[0].toString();
				Date reportDate = (Date) mostAccidentProne[1];
				Date reportLastDate = (Date) mostAccidentProne[2];
				
//				DateTime reportDateTime = new DateTime(reportDate);
//				DateTime reportLastDateTime = new DateTime(reportLastDate);
//				
//				report
//				
//				//Hours hoursInterval = Hours.hoursBetween(reportDateTime, reportLastDateTime);
				Interval interval = getInterval(reportDate, reportLastDate);
				
				double minutesToHourPercentage = (interval == null) ? 0 : interval.toDuration().getStandardMinutes() / (60 * 24 * 1.0);
				mostAccidentProneMap.put("road", Roads.getShortNameByCode(road));
				mostAccidentProneMap.put("fromDate", Utils.formatDate(reportDate, Utils.ganttDateFormatString));
				mostAccidentProneMap.put("toDate", Utils.formatDate(reportLastDate, Utils.ganttDateFormatString));
				//floodReportMap.put("interval", interval.toDuration().getStandardDays() + " " + (interval.toDuration().getStandardHours() - interval.toDuration().getStandardDays() * 24));
				mostAccidentProneMap.put("interval", getCompleteIntervalDescription(interval));
				mostAccidentProneMap.put("durationInMinutes", minutesToHourPercentage);
				
				mostAccidentProneReportList.add(mostAccidentProneMap);
				//System.out.println(road + ": " + reportDate + ": "  + reportLastDate + ": " + interval.toDuration());
			}
		}
		
		return mostAccidentProneReportList;
	}
	
	@Override
	public Map<String, Integer> getAccidentCountsPerStreet(Date startDate, Date endDate) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
		
		List<Object[]> accidentCountList = situationReportDAO.getAccidentCountsPerRoad(startDate, endDate, Status.VALIDATED.getCode());
		
		Map<String, Integer> floodReportMap = new HashMap<String, Integer>();
		
		for(Object[] accidentCount : accidentCountList) {
			if(null != accidentCount[0] && null != accidentCount[1]) {
				
				String road = accidentCount[0].toString();
				Integer count = Integer.parseInt(accidentCount[1].toString());
				
				floodReportMap.put(road, count);
			}
		}
		
		return floodReportMap;
	}
	
	@Override
	public List<Map<String, Object>> getMostCongestedStreets(Date startDate, Date endDate) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(endDate);
		cal.set(cal.HOUR_OF_DAY, 23);
		cal.set(cal.MINUTE, 59);
		cal.set(cal.SECOND, 59);
		endDate = cal.getTime();
		
		List<Object[]> mostCongestedStreetsList = situationReportDAO.getMostCongestedStreets(startDate, endDate, Status.VALIDATED.getCode());
		
		List<Map<String, Object>> mostCongestedStreetReportList = new ArrayList<Map<String, Object>>();
		
		for(Object[] mostCongestedStreet : mostCongestedStreetsList) {
			if(null != mostCongestedStreet[0] && null != mostCongestedStreet[1]) {
				
				Map<String, Object> mostCongestedStreepMap = new HashMap<String, Object>();
				
				String road = mostCongestedStreet[0].toString();
				Date reportDate = (Date) mostCongestedStreet[1];
				Date reportLastDate = (Date) mostCongestedStreet[2];
				String situation = (mostCongestedStreet[3] == null) ? "" : mostCongestedStreet[3].toString();
				
				Interval interval = getInterval(reportDate, reportLastDate);
				
				double minutesToHourPercentage = (interval == null) ? 0 : interval.toDuration().getStandardMinutes() / (60 * 24 * 1.0);
				mostCongestedStreepMap.put("road", Roads.getShortNameByCode(road));
				mostCongestedStreepMap.put("fromDate", Utils.formatDate(reportDate, Utils.ganttDateFormatString));
				mostCongestedStreepMap.put("toDate", Utils.formatDate(reportLastDate, Utils.ganttDateFormatString));
				//floodReportMap.put("interval", interval.toDuration().getStandardDays() + " " + (interval.toDuration().getStandardHours() - interval.toDuration().getStandardDays() * 24));
				mostCongestedStreepMap.put("interval", getCompleteIntervalDescription(interval));
				mostCongestedStreepMap.put("durationInMinutes", minutesToHourPercentage);
				mostCongestedStreepMap.put("situation", situation);
				
				mostCongestedStreetReportList.add(mostCongestedStreepMap);
			}
		}
		
		return mostCongestedStreetReportList;
	}
	
	private String getCompleteIntervalDescription(Interval  interval) {
		if(interval == null) return "0h";
		
		Period period = interval.toPeriod();
		
		String intervalDesc = "";
		
		if(period.getYears() != 0) {
			intervalDesc += period.getYears() + "Y ";
		} 
		if(period.getMonths() != 0) {
			intervalDesc += period.getMonths() + "Mo ";
		} 
		if(period.getDays() != 0) {
			intervalDesc += period.getDays() + "D ";
		} 
		if(period.getHours() != 0) {
			intervalDesc += period.getHours() + "H ";
		} 
		if(period.getMinutes() != 0) {
			intervalDesc += period.getMinutes() + "Mi ";
		} 
		if(period.getSeconds() != 0) {
			intervalDesc += period.getSeconds() + "S ";
		}
		
		//interval.toPeriod().getHours() + " " + interval.toPeriod().getMinutes() + " " + interval.toPeriod().getSeconds());
		return intervalDesc;
	}
	
	private Interval getInterval(Date date1, Date date2) {
		if(date1 != null && date2 != null) {
			DateTime reportDateTime = new DateTime(date1);
			DateTime reportLastDateTime = new DateTime(date2);
			
			//Hours hoursInterval = Hours.hoursBetween(reportDateTime, reportLastDateTime);
			Interval interval = new Interval(reportDateTime, reportLastDateTime);
			return interval;
		}
		return null;
	}
	
	public void setSettingsService(SettingsService settingsService) {
		this.settingsService = settingsService;
	}

}
